package com.yalantis.ucrop.view;



import com.yalantis.ucrop.callback.BitmapCropCallback;
import com.yalantis.ucrop.callback.CropBoundsChangeListener;
import com.yalantis.ucrop.model.CropParameters;
import com.yalantis.ucrop.model.ImageState;
import com.yalantis.ucrop.task.BitmapCropTask;
import com.yalantis.ucrop.util.Config;
import com.yalantis.ucrop.util.CubicEasing;
import com.yalantis.ucrop.util.LogUtils;
import com.yalantis.ucrop.util.NumCalcUtil;
import com.yalantis.ucrop.util.RectUtils;

import java.lang.ref.WeakReference;
import java.util.Arrays;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

/**
 * Created by Oleksii Shliama (https://github.com/shliama).
 * <p/>
 * This class adds crop feature, methods to draw crop guidelines, and keep image in correct state.
 * Also it extends parent class methods to add checks for scale; animating zoom in/out.
 */
public class CropImageView extends TransformImageView {

    public static final int DEFAULT_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION = 500;
    public static final float DEFAULT_MAX_SCALE_MULTIPLIER = 10.0f;
    public static final float SOURCE_IMAGE_ASPECT_RATIO = 0f;

    private final RectFloat mCropRect = new RectFloat();

    private final Matrix mTempMatrix = new Matrix();

    private float mTargetAspectRatio;

    private CropBoundsChangeListener mCropBoundsChangeListener;

    private Runnable mWrapCropBoundsRunnable;

    private float mMaxScale;
    private final EventHandler eventHandler ;

    public CropImageView(Context context, AttrSet attrs, String s) {
        super(context, attrs, "0");
        System.out.println(s);
        EventRunner eventRunner = EventRunner.getMainEventRunner();
        eventHandler = new EventHandler(eventRunner);
    }

    /**
     * cropAndSaveImage
     *
     * @param compressFormat compressFormat
     * @param compressQuality compressQuality
     * @param cropCallback cropCallback
     * @param context context
     */
    public void cropAndSaveImage(String compressFormat, int compressQuality,
                                  BitmapCropCallback cropCallback,Context context) {
        System.out.println(context);
        cancelAllAnimations();
        setImageToWrapCropBounds(false);

        final ImageState imageState = new ImageState(
                mCropRect, RectUtils.trapToRect(mCurrentImageCorners),
                getCurrentScale(), getCurrentAngle());

        int mMaxResultImageSizeX = 0;
        int mMaxResultImageSizeY = 0;
        final CropParameters cropParameters = new CropParameters(
                mMaxResultImageSizeX, mMaxResultImageSizeY,
                compressFormat, compressQuality,
                getImageInputPath(), getImageOutputPath(), getExifInfo());

        BitmapCropTask urlpath = new BitmapCropTask(getContext(), getViewBitmap(), imageState, cropParameters, cropCallback);
        urlpath.onCropResult(cropResult);
        urlpath.doInBackground();
    }
    private CropResult cropResult;
    public void onCropResult(CropResult result) {
        this.cropResult = result;
    }

    /**
     * 获取最大比例
     *
     * @return - maximum scale value for current image and crop ratio
     */
    public float getMaxScale() {
        return mMaxScale;
    }

    /**
     * Updates current crop rectangle with given. Also recalculates image properties and position
     * to fit new crop rectangle.
     *
     * @param cropRect - new crop rectangle
     */
    public void setCropRect(RectFloat cropRect) {
        mTargetAspectRatio = cropRect.getWidth() / cropRect.getHeight();
        mCropRect.modify(cropRect.left - getPaddingLeft(), cropRect.top - getPaddingTop(),
                cropRect.right - getPaddingRight(), cropRect.bottom - getPaddingBottom());
        calculateImageScaleBounds();
        setImageToWrapCropBounds();
    }

    public void setCropBoundsChangeListener( CropBoundsChangeListener cropBoundsChangeListener) {
        mCropBoundsChangeListener = cropBoundsChangeListener;
    }

    /**
     * zoomInImage
     *
     * @param scale scale
     * @param centerX centerX
     * @param centerY centerY
     */
    public void zoomInImage(float scale, float centerX, float centerY) {
        if (scale <= getMaxScale()) {
            postScale(scale / getCurrentScale(), centerX, centerY);
        }
    }

    /**
     * This method changes image scale for given value related to point (px, py) but only if
     * resulting scale is in min/max bounds.
     *
     * @param deltaScale - scale value
     * @param px         - scale center X
     * @param py         - scale center Y
     */
    public void postScale(float deltaScale, float px, float py) {
        if (deltaScale >= 2 && getCurrentScale() * deltaScale <= getMaxScale()) {
            super.postScale(deltaScale, px, py,1);
        } else if (deltaScale < 2) {
            super.postScale(deltaScale, px, py,0);
        }
    }

    /**
     * This method cancels all current Runnable objects that represent animations.
     */
    public void cancelAllAnimations() {

        eventHandler.removeTask(mWrapCropBoundsRunnable);
        eventHandler.removeTask(null);
    }

    public void setImageToWrapCropBounds() {
        setImageToWrapCropBounds(true);
    }

    /**
     * setImageToWrapCropBounds
     *
     * @param animate animate
     */
    public void setImageToWrapCropBounds(boolean animate) {
        if (mBitmapLaidOut && !isImageWrapCropBounds()) {
            //得到当前图像的中心点坐标
            float currentX = mCurrentImageCenter[0];
            float currentY = mCurrentImageCenter[1];
            float currentScale = getCurrentScale();
            //裁剪框的中心点坐标减去图像的中心点坐标
            float deltaX = NumCalcUtil.subtract(mCropRect.getCenter().position[0] , currentX);
            float deltaY = NumCalcUtil.subtract(mCropRect.getCenter().position[1] , currentY);
            float deltaScale = 0;

            mTempMatrix.reset();
            mTempMatrix.setTranslate(deltaX, deltaY);

            final float[] tempCurrentImageCorners = Arrays.copyOf(mCurrentImageCorners, mCurrentImageCorners.length);
            mTempMatrix.mapPoints(tempCurrentImageCorners);

            boolean willImageWrapCropBoundsAfterTranslate = isImageWrapCropBounds(tempCurrentImageCorners);

            if (willImageWrapCropBoundsAfterTranslate) {
                final float[] imageIndents = calculateImageIndents();
                deltaX = -(NumCalcUtil.add(imageIndents[0] , imageIndents[2]));
                deltaY = -(NumCalcUtil.add(imageIndents[1] , imageIndents[3]));
            } else {
                RectFloat tempCropRect = new RectFloat(mCropRect);
                mTempMatrix.reset();
                mTempMatrix.setRotate(getCurrentAngle());
                mTempMatrix.mapRect(tempCropRect);

                final float[] currentImageSides = RectUtils.getRectSidesFromCorners(mCurrentImageCorners);

                deltaScale = Math.max(tempCropRect.getWidth() / currentImageSides[0],
                        tempCropRect.getHeight() / currentImageSides[1]);
                deltaScale = NumCalcUtil.subtract(deltaScale * currentScale , currentScale);
            }

            if (animate) {
                eventHandler.postTask(mWrapCropBoundsRunnable = new WrapCropBoundsRunnable(
                        CropImageView.this, DEFAULT_IMAGE_TO_CROP_BOUNDS_ANIM_DURATION, currentX, currentY, deltaX, deltaY,
                        currentScale, deltaScale, true));
                LogUtils.LogInfo("qqqqqqq:","setImageToWrapCropBounds to setMatrix, DeltaX & DeltaY:"+ deltaX +"/" + deltaY);
            } else {
                postTranslate(deltaX, deltaY);
//                if (!willImageWrapCropBoundsAfterTranslate) {
//                    zoomInImage(currentScale + deltaScale, mCropRect.getCenter().position[0], mCropRect.getCenter().position[1]);
//                }
            }
        }
    }

    /**
     * First, un-rotate image and crop rectangles (make image rectangle axis-aligned).
     * Second, calculate deltas between those rectangles sides.
     * Third, depending on delta (its sign) put them or zero inside an array.
     * Fourth, using Matrix, rotate back those points (indents).
     *
     * @return - the float array of image indents (4 floats) - in this order [left, top, right, bottom]
     */
    private float[] calculateImageIndents() {
        mTempMatrix.reset();
        mTempMatrix.setRotate(-getCurrentAngle());

        float[] unrotatedImageCorners = Arrays.copyOf(mCurrentImageCorners, mCurrentImageCorners.length);
        float[] unrotatedCropBoundsCorners = RectUtils.getCornersFromRect(mCropRect);

        mTempMatrix.mapPoints(unrotatedImageCorners);
        mTempMatrix.mapPoints(unrotatedCropBoundsCorners);

        RectFloat unrotatedImageRect = RectUtils.trapToRect(unrotatedImageCorners);
        RectFloat unrotatedCropRect = RectUtils.trapToRect(unrotatedCropBoundsCorners);

        float deltaLeft = NumCalcUtil.subtract(unrotatedImageRect.left , unrotatedCropRect.left);
        float deltaTop = NumCalcUtil.subtract(unrotatedImageRect.top , unrotatedCropRect.top);
        float deltaRight = NumCalcUtil.subtract(unrotatedImageRect.right , unrotatedCropRect.right);
        float deltaBottom = NumCalcUtil.subtract(unrotatedImageRect.bottom , unrotatedCropRect.bottom);

        float[] indents = new float[4];
        indents[0] = (deltaLeft < 0) ? deltaLeft : 0;
        indents[1] = (deltaTop < 0) ? deltaTop : 0;
        indents[2] = (deltaRight < 0) ? deltaRight : 0;
        indents[3] = (deltaBottom < 0) ? deltaBottom : 0;

        mTempMatrix.reset();
        mTempMatrix.setRotate(getCurrentAngle());
        mTempMatrix.mapPoints(indents);

        return indents;
    }

    /**
     * When image is laid out it must be centered properly to fit current crop bounds.
     */
    @Override
    protected void onImageLaidOut() {
        super.onImageLaidOut();
        final Component drawable = getComponentAt(0);
        if (drawable == null) {
            return;
        }

        float drawableWidth = mBitmapHolder.getPixelMap().getImageInfo().size.width;
        float drawableHeight = mBitmapHolder.getPixelMap().getImageInfo().size.height;
        LogUtils.LogInfo("qqqqqqq:", "drawableWidth" + drawableWidth+ "drawableHeight" +drawableHeight);

        if (mTargetAspectRatio == SOURCE_IMAGE_ASPECT_RATIO) {
            mTargetAspectRatio = drawableWidth / drawableHeight;
        }

        int height = (int) (mThisWidth / mTargetAspectRatio);
        if (height > mThisHeight) {
            int width = (int) (mThisHeight * mTargetAspectRatio);
            int halfDiff = (mThisWidth - width) / 2;
            mCropRect.modify(halfDiff, 0, width + halfDiff, mThisHeight);
        } else {
            int halfDiff = (mThisHeight - height) / 2;
            mCropRect.modify(0, halfDiff, mThisWidth, height + halfDiff);
        }

        calculateImageScaleBounds(drawableWidth, drawableHeight);
        setupInitialImagePosition(drawableWidth, drawableHeight);

        if (mCropBoundsChangeListener != null) {
            mCropBoundsChangeListener.onCropAspectRatioChanged(mTargetAspectRatio);
        }
        if (mTransformImageListener != null) {
            mTransformImageListener.onScale(getCurrentScale());
            mTransformImageListener.onRotate(getCurrentAngle());
        }
    }

    /**
     * isImageWrapCropBounds
     *
     * @return isImageWrapCropBounds
     * @noinspection checkstyle:JavadocMethod
     */
    public boolean isImageWrapCropBounds() {
        return isImageWrapCropBounds(mCurrentImageCorners);
    }

    /**
     * This methods checks whether a rectangle that is represented as 4 corner points (8 floats)
     * fills the crop bounds rectangle.
     *
     * @param imageCorners - corners of a rectangle
     * @return - true if it wraps crop bounds, false - otherwise
     */
    protected boolean isImageWrapCropBounds(float[] imageCorners) {
        LogUtils.LogInfo("qqqqqqq:","imageCorners" + Arrays.toString(imageCorners));
        mTempMatrix.reset();
        mTempMatrix.setRotate(-getCurrentAngle());

        float[] unrotatedImageCorners = Arrays.copyOf(imageCorners, imageCorners.length);
        mTempMatrix.mapPoints(unrotatedImageCorners);

        float[] unrotatedCropBoundsCorners = RectUtils.getCornersFromRect(mCropRect);
        mTempMatrix.mapPoints(unrotatedCropBoundsCorners);

        return RectUtils.trapToRect(unrotatedImageCorners).isInclude(RectUtils.trapToRect(unrotatedCropBoundsCorners));
    }

    private void calculateImageScaleBounds() {
        final Component drawable = getComponentAt(0);
        if (drawable == null) {
            return;
        }
        calculateImageScaleBounds(404, 606);
    }

    /**
     * This method calculates image minimum and maximum scale values for current {@link #mCropRect}.
     *
     * @param drawableWidth  - image width
     * @param drawableHeight - image height
     */
    private void calculateImageScaleBounds(float drawableWidth, float drawableHeight) {
//        float widthScale = Math.min(NumCalcUtil.divide(mCropRect.getWidth() , drawableWidth), NumCalcUtil.divide(mCropRect.getWidth() , drawableHeight));
//        float heightScale = Math.min(NumCalcUtil.divide(mCropRect.getHeight() , drawableHeight), NumCalcUtil.divide(mCropRect.getHeight() , drawableWidth));
        float widthScale = Math.min(mCropRect.getWidth() / drawableWidth, mCropRect.getWidth() / drawableHeight);
        float heightScale = Math.min(mCropRect.getHeight() / drawableHeight, mCropRect.getHeight() / drawableWidth);
        float mMinScale = Math.min(widthScale, heightScale);
        mMaxScale = mMinScale * DEFAULT_MAX_SCALE_MULTIPLIER;
    }

    /**
     * This method calculates initial image position so it is positioned properly.
     * Then it sets those values to the current image matrix.
     *
     * @param drawableWidth  - image width
     * @param drawableHeight - image height
     */
    private void setupInitialImagePosition(float drawableWidth, float drawableHeight) {
        float cropRectWidth = mCropRect.getWidth();
        float cropRectHeight = mCropRect.getHeight();

        float widthScale = mCropRect.getWidth() / drawableWidth;
        float heightScale = mCropRect.getHeight() / drawableHeight;

        float initialMinScale = Math.max(widthScale, heightScale);

        float tw = NumCalcUtil.add((NumCalcUtil.subtract(cropRectWidth , drawableWidth * initialMinScale)) / 2.0f
                , mCropRect.left);
        float th = NumCalcUtil.add((NumCalcUtil.subtract(cropRectHeight , drawableHeight * initialMinScale)) / 2.0f
                , mCropRect.top);

        mCurrentImageMatrix.reset();
        mCurrentImageMatrix.postScale(initialMinScale, initialMinScale);
        mCurrentImageMatrix.postTranslate(tw, th);
        setImageMatrix(mCurrentImageMatrix);
        LogUtils.LogInfo("qqqqqqq:","CropImageView to setMatrix");
    }

    /**
     * processStyledAttributes
     *
     * @param a a
     */
    protected void processStyledAttributes( Config a) {
//        float targetAspectRatioX = Math.abs(a.getFloat(R.styleable.ucrop_UCropView_ucrop_aspect_ratio_x, DEFAULT_ASPECT_RATIO));
//        float targetAspectRatioY = Math.abs(a.getFloat(R.styleable.ucrop_UCropView_ucrop_aspect_ratio_y, DEFAULT_ASPECT_RATIO));
        float targetAspectRatioX = a.getTargetAspectRatioX();
        float targetAspectRatioY = a.getTargetAspectRatioY();
        if (targetAspectRatioX == SOURCE_IMAGE_ASPECT_RATIO || targetAspectRatioY == SOURCE_IMAGE_ASPECT_RATIO) {
            mTargetAspectRatio = SOURCE_IMAGE_ASPECT_RATIO;
        } else {
            mTargetAspectRatio = targetAspectRatioX / targetAspectRatioY;
        }
    }

    /**
     * This Runnable is used to animate an image so it fills the crop bounds entirely.
     * Given values are interpolated during the animation time.
     * Runnable can be terminated either vie {@link #cancelAllAnimations()} method
     * or when certain conditions inside {@link WrapCropBoundsRunnable#run()} method are triggered.
     */
    private static class WrapCropBoundsRunnable implements Runnable {

        private final WeakReference<CropImageView> mCropImageView;

        private final long mDurationMs, mStartTime;
        private final float mOldX, mOldY;
        private final float mCenterDiffX, mCenterDiffY;
        private final float mOldScale;
        private final float mDeltaScale;
        private final boolean mWillBeImageInBoundsAfterTranslate;

        public WrapCropBoundsRunnable(CropImageView cropImageView,
                                      long durationMs,
                                      float oldX, float oldY,
                                      float centerDiffX, float centerDiffY,
                                      float oldScale, float deltaScale,
                                      boolean willBeImageInBoundsAfterTranslate) {

            mCropImageView = new WeakReference<>(cropImageView);

            mDurationMs = durationMs;
            mStartTime = System.currentTimeMillis();
            mOldX = oldX;
            mOldY = oldY;
            mCenterDiffX = centerDiffX;
            mCenterDiffY = centerDiffY;
            mOldScale = oldScale;
            mDeltaScale = deltaScale;
            mWillBeImageInBoundsAfterTranslate = willBeImageInBoundsAfterTranslate;
        }

        @Override
        public void run() {
            CropImageView cropImageView = mCropImageView.get();
            if (cropImageView == null) {
                return;
            }

            long now = System.currentTimeMillis();
            float currentMs = Math.min(mDurationMs, now - mStartTime);

            float newX = CubicEasing.easeOut(currentMs, 0, mCenterDiffX, mDurationMs);
            float newY = CubicEasing.easeOut(currentMs, 0, mCenterDiffY, mDurationMs);
            float newScale = CubicEasing.easeInOut(currentMs, 0, mDeltaScale, mDurationMs);

            if (currentMs < mDurationMs) {
                cropImageView.postTranslate(NumCalcUtil.subtract(newX
                        , (NumCalcUtil.subtract(cropImageView.mCurrentImageCenter[0] , mOldX)))
                        , NumCalcUtil.subtract(newY
                                , (NumCalcUtil.subtract(cropImageView.mCurrentImageCenter[1] , mOldY))));
                if (!mWillBeImageInBoundsAfterTranslate) {
                    cropImageView.zoomInImage(NumCalcUtil.add(mOldScale , newScale)
                            , cropImageView.mCropRect.getCenter().position[0]
                            ,  cropImageView.mCropRect.getCenter().position[1]);
                }
                if (!cropImageView.isImageWrapCropBounds()) {
                    cropImageView.eventHandler.postTask(this);
                }
            }
        }
    }

}
